package com.example.parking.boy;

import com.example.parking.*;
import com.example.parking.car.Car;
import com.example.parking.lot.ParkingLot;
import com.example.parking.lot.ParkingLotGroup;
import com.example.parking.strategy.FirstAvailableParkingLotSelectStrategy;
import com.example.parking.strategy.ParkingLotSelectStrategy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Optional;

public abstract class ParkingBoy<T extends ParkingLotSelectStrategy> implements CarParkable {

    private ParkingLotGroup parkingLotGroup;

    private ParkingLotSelectStrategy parkingLotSelectStrategy;

    public ParkingBoy(ParkingLotGroup parkingLotGroup) {
        this.parkingLotGroup = parkingLotGroup;
        try {
            this.parkingLotSelectStrategy = getParkingStrategyClass().getConstructor().newInstance();
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            this.parkingLotSelectStrategy = new FirstAvailableParkingLotSelectStrategy();
        }
    }

    public void assignParkingLotGroup(ParkingLotGroup parkingLotGroup) {
        this.parkingLotGroup = parkingLotGroup;
    }

    public Token park(Car car) throws ParkingException {
        if (!parkingLotGroup.isParkingLotGroupAvailable()) {
            throw new ParkingException();
        }
        ParkingLot parkingLot = parkingLotSelectStrategy.chooseParkingLot(parkingLotGroup.getParkingLots());
        return parkingLotGroup.park(parkingLot, car);
    }

    public Optional<Car> retrieveCar(Token token) throws ParkingException {
        return parkingLotGroup.retrieveCar(token);
    }

    private Class<? extends ParkingLotSelectStrategy> getParkingStrategyClass() {
        Type type = this.getClass().getGenericSuperclass();
        ParameterizedType p = (ParameterizedType) type;
        //noinspection unchecked
        return (Class<? extends ParkingLotSelectStrategy>) p.getActualTypeArguments()[0];
    }

    public void showReport() {
        System.out.println("B "
                + this.parkingLotGroup.retrieveParkingLotCarCount()
                + " "
                + this.parkingLotGroup.retrieveParkingLotCount()
        );
        this.parkingLotGroup.showReport();
    }
}
